/*
 * Copyright (c) 2006-2018, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 */

#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>

#include "board.h"
#include "drv_uart.h"

#include <stdio.h>
#include <sysctl.h>

#include "uart.h"
#include "uarths.h"
#include "plic.h"

struct d_uart
{
    struct rt_serial_device ser;
    union
    {
        volatile uart_t *hw_base;
        volatile uarths_t *hs;
    };

    uint32_t irqno;
    uart_device_number_t devno;
};

/* UART ISR */
static void uart_irq_handler(void *ctx)
{
    struct d_uart *uart = (struct d_uart*)ctx;
    uint32_t ints;

    ints = uart->hw_base->IIR;

    if (ints & 2) //send
    {
        rt_hw_serial_isr(&uart->ser, RT_SERIAL_EVENT_TX_DONE);
    }

    if (ints & 4) //receive
    {
        rt_hw_serial_isr(&uart->ser, RT_SERIAL_EVENT_RX_IND);
    }
}

static void d_uart_intrx(struct d_uart *uart, int en)
{
    if (en)
    {
        uart->hw_base->IER |= 0x1;
    }
    else
    {
        uart->hw_base->IER &= ~0x1;
    }
}

static void d_uart_inttx(struct d_uart *uart, int en)
{
    if (en)
    {
        uart->hw_base->IER |= 0x2;
    }
    else
    {
        uart->hw_base->IER &= ~0x2;
    }
}

/*
 * UART interface
 */
static int d_uart_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    uart_configure(uart->devno, 115200, UART_BITWIDTH_8BIT,
                  UART_STOP_1, UART_PARITY_NONE);

    return 0;
}

static int d_uart_control(struct rt_serial_device *serial, int cmd, void *arg)
{

    return -1;
}

static int d_uart_putc(struct rt_serial_device *serial, char c)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    while (uart->hw_base->LSR & (1u << 5));

    uart->hw_base->THR = c;

    return (1);
}

static int d_uart_getc(struct rt_serial_device *serial)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;
    int ch = -1;

    if (uart->hw_base->LSR & 1)
        ch = (uart->hw_base->RBR & 0xff);

    return ch;
}

static int d_uart_init(struct rt_serial_device *serial)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    uart_init(uart->devno);

    plic_set_priority(uart->irqno, 1);
    plic_irq_register(uart->irqno, uart_irq_handler, uart);
    plic_irq_enable(uart->irqno);

    return 0;
}

static void d_uart_deinit(struct rt_serial_device *serial)
{

}

static int d_uart_xmitctl(struct rt_serial_device *serial, int cmd, int arg)
{
    int ret = 0;
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    switch (cmd)
    {
    case UART_XMIT_RX:
    {
        d_uart_intrx(uart, arg);
    }break;
    case UART_XMIT_TX:
    {
        d_uart_inttx(uart, arg);
    }break;
    default:
    {
        ret = -1;
    }break;
    }

    return ret;
}

static const struct rt_uart_ops _uart_ops =
{
    d_uart_configure,
    d_uart_control,
    d_uart_putc,
    d_uart_getc,
    d_uart_init,
    d_uart_deinit,
    d_uart_xmitctl
};

#ifdef BSP_USING_UART_HS
static void uarths_irq_handler(void *ctx)
{
    struct d_uart *uart = (struct d_uart*)ctx;

    if (uart->hs->ip.txwm) //send
    {
        rt_hw_serial_isr(&uart->ser, RT_SERIAL_EVENT_TX_DONE);
    }

    if (uart->hs->ip.rxwm) //receive
    {
        rt_hw_serial_isr(&uart->ser, RT_SERIAL_EVENT_RX_IND);
    }
}

static void d_uarths_intrx(struct d_uart *uart, int en)
{
    if (en)
    {
        uart->hs->ie.rxwm = 1;
    }
    else
    {
        uart->hs->ie.rxwm = 0;
    }
}

static void d_uarths_inttx(struct d_uart *uart, int en)
{
    if (en)
    {
        uart->hs->ie.txwm = 1;
    }
    else
    {
        uart->hs->ie.txwm = 0;
    }
}

/*
 * UART interface
 */
static int d_uarths_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    uarths_config(115200, UARTHS_STOP_1);

    return 0;
}

static int d_uarths_control(struct rt_serial_device *serial, int cmd, void *arg)
{

    return -1;
}

static int d_uarths_putc(struct rt_serial_device *serial, char c)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    uarths_putchar(c);

    return (1);
}

static int d_uarths_getc(struct rt_serial_device *serial)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;
    int ch = -1;

    ch = uarths_getchar();

    return ch;
}

static int d_uarths_init(struct rt_serial_device *serial)
{
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    uarths_init();
    uarths_set_interrupt_cnt(UARTHS_SEND, 1);

    plic_set_priority(uart->irqno, 1);
    plic_irq_register(uart->irqno, uarths_irq_handler, uart);
    plic_irq_enable(uart->irqno);

    return 0;
}

static void d_uarths_deinit(struct rt_serial_device *serial)
{

}

static int d_uarths_xmitctl(struct rt_serial_device *serial, int cmd, int arg)
{
    int ret = 0;
    struct d_uart *uart = (struct d_uart*)serial->parent.user_data;

    switch (cmd)
    {
    case UART_XMIT_RX:
    {
        d_uarths_intrx(uart, arg);
    }break;
    case UART_XMIT_TX:
    {
        d_uarths_inttx(uart, arg);
    }break;
    default:
    {
        ret = -1;
    }break;
    }

    return ret;
}

static const struct rt_uart_ops _uarths_ops =
{
    d_uarths_configure,
    d_uarths_control,
    d_uarths_putc,
    d_uarths_getc,
    d_uarths_init,
    d_uarths_deinit,
    d_uarths_xmitctl
};
#endif

/* WEAK for SDK 0.5.6 */

RT_WEAK void uart_debug_init(uart_device_number_t uart_channel)
{

}

void rt_hw_console_output(char c)
{
    uarths_putchar(c);
}

int rt_hw_console_init(void)
{
    uarths_init();
    uarths_config(115200, UARTHS_STOP_1);
}
INIT_BOARD_EXPORT(rt_hw_console_init);

/*
 * UART Initiation
 */
int rt_hw_uart_init(void)
{
    struct d_uart *uart;

#ifdef BSP_USING_UART_HS
    {
        static struct d_uart _uarths;

        uart    = &_uarths;

        uart->hs    = (volatile uarths_t *)UARTHS_BASE_ADDR;
        uart->irqno = IRQN_UARTHS_INTERRUPT;
        uart->ser.ops   = &_uarths_ops;

        rt_hw_serial_register(&uart->ser, "uarths", 0, uart);
    }
#endif

#ifdef BSP_USING_UART1
    {
        static struct d_uart _uart1;

        uart    = &_uart1;

        uart->hw_base   = (volatile uart_t*)UART1_BASE_ADDR;
        uart->irqno     = IRQN_UART1_INTERRUPT;
        uart->devno     = UART_DEVICE_1;

        uart->ser.ops   = &_uart_ops;

        rt_hw_serial_register(&uart->ser, "uart1", 0, uart);                   
    }
#endif

    return 0;
}
INIT_DEVICE_EXPORT(rt_hw_uart_init);
